#!/usr/bin/env python
# -*- coding: utf8 -*-
'''Scan the directory tree and find all users and groups owning
files.

Get a filename. Get the user and group names.
If there is no entry for a name, create one.
If the name is known, increase its counter.
'''

import os.path
import stat
import SortedBundledList as sbl
from optparse import OptionParser
import pwd
import grp

class IdCountSize():
    '''Define a class counting uid/gid occurences and total size.'''

    def __init__(self, ugid, size):
        self.ugid = ugid
        self.count = 1
        self.size = size

    def __str__(self):
        return '%d::(x%d):%d' % (self.ugid, self.count, self.size)

    def cmp(self, element):
        '''Compare self to value, return negative, 0, or positive
        if less, equal, or greater, respectively.'''
        return cmp(self.ugid, element.ugid)

    def list(self):
        '''This class is a list-like class.'''
        return self

    def append(self, element):
        '''Count instancies of same value.'''
        assert self.ugid == element.ugid
        self.count += 1
        self.size += element.size

class User(IdCountSize):
    "uid, number of files with this uid, sum of file sizes"

    def __str__(self):
        try:
            _user = pwd.getpwuid(self.ugid)[0]
        except KeyError:
            _user = "?"
        return '%d:%s:(x%d):%d' % (self.ugid, _user, self.count, self.size)

class Group(IdCountSize):
    "gid, number of files with this gid, sum of file sizes"

    def __str__(self):
        try:
            _group = grp.getgrgid(self.ugid)[0]
        except KeyError:
            _group = "?"
        return '%d:%s:(x%d):%d' % (self.ugid, _group, self.count, self.size)

class Users():
    """Hold the information about the files with the same content."""

    def __init__(self):
        self.fname = None      # current file name
        self.stat = None       # result of lstat(self.fname)
        self.users = sbl.SortedBundledList()
        self.groups = sbl.SortedBundledList()
        self.skipped = []

    def __str__(self):
        return "Skipped:\n%s\n\nUsers:\n%s\n\nGroups:\n%s\n" % \
                    (str(self.skipped), str(self.users), str(self.groups))

    def is_normal(self):
        "Check if current filename should be inserted into the object."
        try:
            self.stat = os.lstat(self.fname)
        except IOError:
            sbl.log('Cannot lstat file %s, skip.' % self.fname, 10,
                    'is_normal:')
            self.skipped.append(self.fname)
            return False
        return True

    def insert(self, fname):
        "Insert a filename uid/gid into the object."
        self.fname = fname
        self.stat = None
        if not self.is_normal():
            return    # skip special cases
        self.users.insert(User(self.stat[stat.ST_UID], self.stat[stat.ST_SIZE]))
        self.groups.insert(Group(self.stat[stat.ST_GID],
                                 self.stat[stat.ST_SIZE]))

def parse_args():
    '''Set a parser up and parse the args.'''
    parser = OptionParser()
    #parser.add_option("-f", "--file", dest="filename",
    #                  help="write report to FILE", metavar="FILE")
    parser.add_option("-x", "--onefs",
                      action="store_true", dest="onefs", default=False,
                      help="do not descend to dirs on other filesystems")
    parser.add_option("-s", "--skip", default=[],
                      action="append", dest="skip", metavar="SKIP",
                      help="do not descend into the dir named SKIP")
    parser.add_option("-d", "--debug", default=0, metavar="LEVEL",
                      dest="debug", help="set debug level to LEVEL")
    return parser.parse_args()

def main():
    '''Do the job, i.e. parse params, search, and print results.'''
    options, args = parse_args()
    if args is None or args == []:
        args = ['.']
    users = Users()
    if options.debug:
        sbl.DEBUG = eval('int(' + str(options.debug) + ')')
    sbl.log(sbl.show(options), 50, 'main:')
    for arg in args:
        for root, dirs, files in os.walk(arg):
            for fil in files + dirs:
                try:
                    sbl.log("root: %s, file: %s." % (root, fil), 10, 'main')
                    users.insert(os.path.join(root, fil))
                except StandardError, err:
                    sbl.log("Error: %s; file: %s/%s." % \
                          (err, str(root), str(fil)), 1, 'main_0:')
                    users.skipped.append(os.path.join(root, fil))
            for cat in options.skip:
                if cat in dirs:
                    dirs.remove(cat)
            if options.onefs:
                for cat in dirs[:]:
                    if os.path.ismount(cat):
                        dirs.remove(cat)
    print users
    sbl.log(sbl.show(users), 99, 'main:')

if __name__ == '__main__':
    main()
